using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace Trentacular.SharePoint.Util
{
	public static class TypeUtils
    {
		/// <summary>
		/// Returns an Object with the specified Type and whose value is equivalent to the specified object.
		/// </summary>
		/// <param name="value">An Object that implements the IConvertible interface.</param>
		/// <param name="conversionType">The Type to which value is to be converted.</param>
		/// <returns>An object whose Type is conversionType (or conversionType's underlying type if conversionType
		/// is Nullable&lt;&gt;) and whose value is equivalent to value. -or- a null reference, if value is a null
		/// reference and conversionType is not a value type.</returns>
		/// <remarks>
		/// This method exists as a workaround to System.Convert.ChangeType(Object, Type) which does not handle
		/// nullables as of version 2.0 (2.0.50727.42) of the .NET Framework. The idea is that this method will
		/// be deleted once Convert.ChangeType is updated in a future version of the .NET Framework to handle
		/// nullable types, so we want this to behave as closely to Convert.ChangeType as possible.
		/// This method was written by Peter Johnson at:
		/// http://aspalliance.com/author.aspx?uId=1026.
		/// </remarks>
		public static object ChangeType(object value, Type conversionType)
        {
			// Note: This if block was taken from Convert.ChangeType as is, and is needed here since we're
			// checking properties on conversionType below.
			if (conversionType == null) {
				throw new ArgumentNullException("conversionType");
			} // end if

			// If it's not a nullable type, just pass through the parameters to Convert.ChangeType

			if (IsNullable(conversionType)) {
				// It's a nullable type, so instead of calling Convert.ChangeType directly which would throw a
				// InvalidCastException (per http://weblogs.asp.net/pjohnson/archive/2006/02/07/437631.aspx),
				// determine what the underlying type is
				// If it's null, it won't convert to the underlying type, but that's fine since nulls don't really
				// have a type--so just return null
				// Note: We only do this check if we're converting to a nullable type, since doing it outside
				// would diverge from Convert.ChangeType's behavior, which throws an InvalidCastException if
				// value is null and conversionType is a value type.
				if (value == null) {
					return null;
				} // end if

				// It's a nullable type, and not null, so that means it can be converted to its underlying type,
				// so overwrite the passed-in conversion type with this underlying type

                conversionType = GetNullableUnderlyingType(conversionType);
			} // end if

            if (conversionType.IsEnum)
                return Enum.Parse(conversionType, value.ToString());

			// Now that we've guaranteed conversionType is something Convert.ChangeType can handle (i.e. not a
			// nullable type), pass the call on to Convert.ChangeType
			return Convert.ChangeType(value, conversionType);
		}

        public static bool IsNullable(this Type type)
        {
            return
                type.IsGenericType &&
                type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
        }

        public static Type GetNullableUnderlyingType(this Type type)
        {
            NullableConverter nullableConverter = new NullableConverter(type);
            return nullableConverter.UnderlyingType;
        }

        public static bool IsNullOrEmpty(object o)
        {
            if (o == null) return true;
            if (o is string)
            {
                return string.IsNullOrEmpty((string)o);
            }
            if (o is DBNull)
                return true;
            return false;
        }

        private static readonly Func<MethodInfo, IEnumerable<Type>> ParameterTypeProjection =
            method => method.GetParameters()
                            .Select(p => p.ParameterType.IsGenericType ? p.ParameterType.GetGenericTypeDefinition() : p.ParameterType);

        public static MethodInfo GetGenericMethod(this Type type, string name, params Type[] parameterTypes)
        {
            return (from method in type.GetMethods()
                    where method.Name == name
                    where method.IsGenericMethod
                    where parameterTypes.SequenceEqual(ParameterTypeProjection(method))
                    select method).SingleOrDefault();
        }

        private static readonly BindingFlags PropertyFlags = BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
        private static readonly BindingFlags ProxiedPropertyFlags = BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly;

        private static BindingFlags ResolveFlagsToUse(Type type)
        {
            if (type.Assembly.FullName.StartsWith("DynamicAssemblyProxyGen"))
            {
                return ProxiedPropertyFlags;
            }
            return PropertyFlags;
        }

        public static PropertyInfo[] GetAllPublicInstanceProperties(this Type type)
        {
            return type.GetProperties(ResolveFlagsToUse(type));
        }

        public static PropertyInfo GetPublicInstanceProperty(this Type type, string name)
        {
            return type.GetProperty(name, ResolveFlagsToUse(type));
        }

        private static object[] GetCustomAttributesInternal<T>(MemberInfo member, bool inherit)
        {
            return member.GetCustomAttributes(typeof(T), inherit);
        }

        public static T GetFirstCustomAttribute<T>(this MemberInfo member, bool inherit)
        {
            return (T)GetCustomAttributesInternal<T>(member, inherit).FirstOrDefault();
        }

        public static IEnumerable<T> GetCustomAttributes<T>(this MemberInfo member, bool inherit)
        {
            return GetCustomAttributesInternal<T>(member, inherit).Cast<T>();
        }

        public static object GetDefaultValue(this Type type)
        {
            return typeof(TypeUtils).GetGenericMethod("GetDefaultValue")
                .MakeGenericMethod(type)
                .Invoke(null, null);
        }

        public static T GetDefaultValue<T>()
        {
            return default(T);
        }
	}
}
